﻿/***********************************************************************************\
| Copyright (c) 2011, Bashar Al-Rawi                                                |
| All rights reserved.                                                              |
|                                                                                   |
| Redistribution and use in source and binary forms, with or without                |
| modification, are permitted provided that the following conditions are met:       |
|    * Redistributions of source code must retain the above copyright               |
|      notice, this list of conditions and the following disclaimer.                |
|    * Redistributions in binary form must reproduce the above copyright            |
|      notice, this list of conditions and the following disclaimer in the          |
|      documentation and/or other materials provided with the distribution.         |
|    * Neither the name of the <organization> nor the                               |
|      names of its contributors may be used to endorse or promote products         |
|      derived from this software without specific prior written permission.        |
|                                                                                   |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND   |
| ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED     |
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE            |
| DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY                |
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES        |
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;      |
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND       |
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT        |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS     |
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                      |
\***********************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Xml.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Resources;
using System.Diagnostics;
using Microsoft.Phone.Controls;
using System.Threading;

namespace Arabic.Controls
{
    public partial class Keyboard : UserControl
    {
        private Key bigKey;
        private const int SHIFT_CODE = 233;
        private const int SYM_CODE = 0;
        private const int SYM2_CODE = 380;
        private const int BACKSPACE_CODE = 8;
        private bool isBackspaceOn;
        private bool isSomeKeyOn;
        private int backspaceInvokeCount;
        private IDictionary<LayoutId, IList<StackPanel>> layouts;
        public new event Arabic.Controls.Key.KeyDownEventHandler KeyUp;
        public event EventHandler KeyboardHide;
        public event EventHandler KeyboardShow;
        Timer backspaceTimer;
        
        public Keyboard()
        {
            this.DataContext = SIP.Instance;
            InitializeComponent();
            layouts = new Dictionary<LayoutId, IList<StackPanel>>();
        }

        public void Keyboard__OrientationChanged(object sender, OrientationChangedEventArgs e)
        {
            UpdateKeyboardLayouts(true);// (SIP.Instance.ActiveLayout.Id);
            SIP.Instance.Update(true);
        }

        private void InitializeLayout(KeyboardLayout layout)
        {
            LayoutId id = layout.Id;
            IList<StackPanel> panels = new List<StackPanel>();
            Regex rx = new Regex(@"\\[uU]([0-9A-F]{4})");

            foreach (var row in layout.KeyRows)
            {
                StackPanel sp = new StackPanel();
                sp.Orientation = Orientation.Horizontal;
                sp.HorizontalAlignment = System.Windows.HorizontalAlignment.Center;

                panels.Add(sp);
                foreach (var kbdKey in row.Keys)
                {
                    try
                    {
                        Key k = new Key(kbdKey);
                        
                        kbdKey.Text = rx.Replace
                            (
                                kbdKey.Text,
                                delegate(Match match)
                                {
                                    return ((char)Int32.Parse(match.Value.Substring(2), NumberStyles.HexNumber)).ToString();
                                }
                            );
                        //k.DataContext = kbdKey;
                        k.KeyDown += new Key.KeyDownEventHandler(Key_KeyDown);
                        k.KeyUp += new Key.KeyDownEventHandler(Key_KeyUp);
                        if (kbdKey.HasImage)
                        {
                            k.image.Opacity = 1;
                            k.textBlock.Opacity = 0;
                        }
                        else
                        {
                            k.image.Opacity = 0;
                            k.textBlock.Opacity = 1;
                        }
                        sp.Children.Add(k);
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine("error reading key: " + ex.Message);
                        throw;
                    }
                }
            }
            layout.Update(true);
            layouts.Add(id, panels);
        }

        public void FirstInitialize()
        {
            KeyboardLayout layout = SIP.Instance.ActiveLayout;
            InitializeLayout(layout);

            foreach (var sp in layouts[SIP.Instance.ActiveLayout.Id])
            {
                stackPanel.Children.Add(sp);
            }
        }

        public void InitializeKeyboard()
        {

            // Initialize the big key
            ScaleTransform st = new ScaleTransform();
            bigKey = new Key();
            st.ScaleX = 1.6;
            st.ScaleY = 1.1;
            bigKey.RenderTransform = st;
            bigKey.rectangle.StrokeThickness = 2;
            bigKey.rectangle.Fill = ApplicationGlobals.FgBrush;
            bigKey.textBlock.Foreground = ApplicationGlobals.BgBrush;
            bigKey.IsEnabled = false;
            bigKey.Opacity = 0;
            SIP.Instance.Update(true);

            canvas.Children.Add(bigKey);
        }

        private void UpdateKeyboardLayouts(Object notifyUI)
        {
            foreach (var layout in SIP.Instance.Layouts.Values)
            {
                if(layout != SIP.Instance.ActiveLayout)
                    layout.Update((bool)notifyUI);
            }
            
        }

        private void ResetBackspaceTimer()
        {
            if (isBackspaceOn == false)
            {
                if (backspaceTimer != null)
                {
                    lock (backspaceTimer)
                    {
                        if (backspaceTimer != null)
                        {
                            backspaceTimer.Dispose();
                            backspaceTimer = null;
                        }
                    }
                }
                backspaceInvokeCount = 0;
            }
        }

        private void BackSpaceIsOn(Object stateInfo)
        {
            ResetBackspaceTimer();

            List<Object> state = (List<Object>)stateInfo;
            KbdEventArgs e = (KbdEventArgs)state[1];
            backspaceInvokeCount++;
            if (backspaceInvokeCount > 7)
            {
                backspaceTimer.Change(100, 100);
            }
            
            Key downKey = state[0] as Key;
            Dispatcher.BeginInvoke(() => downKey.PlaySound());
            Dispatcher.BeginInvoke(() => KeyUp(downKey, e));
            
        }

        private void Key_KeyDown(object sender, KbdEventArgs e)
        {
            Key downKey = sender as Key;
            isSomeKeyOn = true;
            KeyboardKey keyModel = downKey.DataContext as KeyboardKey;
            if (downKey != null && keyModel != null && !SIP.Instance.IsSpecialKey(keyModel))
            {
                GeneralTransform gt = downKey.TransformToVisual(canvas);
                Point offset = gt.Transform(new Point(0, 0));
                bigKey.DataContext = keyModel;
                Canvas.SetLeft(bigKey, offset.X - (downKey.Width * 0.3));
                Canvas.SetTop(bigKey, offset.Y - downKey.ActualHeight - (downKey.Height * 0.1));
                bigKey.Opacity = 1;
            }
                

            if(SIP.Instance.IsBackspace(keyModel))
            {
                if (!isBackspaceOn)
                {
                    isBackspaceOn = true;
                    backspaceInvokeCount = 0;
                    backspaceTimer = new Timer(BackSpaceIsOn, new List<Object>() { sender, e }, 200, 200);
                    if(KeyUp != null)
                        KeyUp(sender, e);
                }

            }

        }

        private void HideBigKey(Object stateInfo)
        {
            if (bigKey != null && !isSomeKeyOn)
            {
                Dispatcher.BeginInvoke(() => {bigKey.Opacity = 0;});
            }
        }

        private void Key_KeyUp(object sender, KbdEventArgs e)
        {
            isSomeKeyOn = false;
            Timer t = new Timer(HideBigKey, null, 50, Timeout.Infinite);

            if (e != null)
            {
                if (e.PlatformkeyCode == SHIFT_CODE ||
                    e.PlatformkeyCode == SYM_CODE || e.PlatformkeyCode == SYM2_CODE)
                {
                    switch (e.PlatformkeyCode)
                    {
                        case SHIFT_CODE:
                            if (SIP.Instance.ActiveLayout.Id == LayoutId.Capital)
                            {
                                SIP.Instance.SwitchLayout(LayoutId.Small);
                                SIP.Instance.IsShiftOn = false;
                            }
                            else
                            {
                                SIP.Instance.SwitchLayout(LayoutId.Capital);
                                SIP.Instance.IsShiftOn = true;
                            }
                            break;
                        case SYM_CODE:
                            if (SIP.Instance.ActiveLayout.Id == LayoutId.Symbols1 || SIP.Instance.ActiveLayout.Id == LayoutId.Symbols2)
                            {
                                if (SIP.Instance.IsShiftOn)
                                {
                                    SIP.Instance.SwitchLayout(LayoutId.Capital);
                                }
                                else
                                {
                                    SIP.Instance.SwitchLayout(LayoutId.Small);
                                }
                            }
                            else
                            {
                                SIP.Instance.SwitchLayout(LayoutId.Symbols1);
                            }
                            break;
                        case SYM2_CODE:
                            if (SIP.Instance.ActiveLayout.Id == LayoutId.Symbols1)
                            {
                                SIP.Instance.SwitchLayout(LayoutId.Symbols2);
                            }
                            else
                            {
                                SIP.Instance.SwitchLayout(LayoutId.Symbols1);
                            }
                            break;
                    }

                    Dispatcher.BeginInvoke(() => { SwitchKeyboardKeys(); });
                    e.Handled = true;
                }
                else if (e.PlatformkeyCode == BACKSPACE_CODE)
                {
                    e.Handled = true;
                }

                if (!e.Handled)
                {
                    KeyUp(sender, e);
                }
            }
            isBackspaceOn = false;
            ResetBackspaceTimer();
        }

        private void SwitchKeyboardKeys()
        {
            try
            {
                if (!layouts.ContainsKey(SIP.Instance.ActiveLayout.Id))
                {
                    InitializeLayout(SIP.Instance.ActiveLayout);
                }

                // make sure there are the same number of rows
                IList<StackPanel> newPanel = layouts[SIP.Instance.ActiveLayout.Id];
                while (newPanel.Count > stackPanel.Children.Count)
                {
                    int newRows = newPanel.Count - stackPanel.Children.Count;
                    for (int i = 0; i < newRows; i++)
                    {
                        stackPanel.Children.Add(newPanel[stackPanel.Children.Count + i]);
                    }
                }
                while (newPanel.Count < stackPanel.Children.Count)
                {
                    int newRows = stackPanel.Children.Count - newPanel.Count;
                    for (int i = 0; i < newRows; i++)
                    {
                        stackPanel.Children.RemoveAt(stackPanel.Children.Count - 1);
                    }
                }

                // go over each raw and update the keys

                for (int i = 0; i < stackPanel.Children.Count; i++)
                {
                    stackPanel.Children[i] = newPanel[i];
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
        }

        public void Show()
        {
            SIP.Instance.Show();
            SlideKeyFrame.Value = -this.Height;
            KeyboardSlide.Begin();
            KeyboardShow(this, null);
        }

        public void Hide()
        {
            SlideKeyFrame.Value = this.Height;
            KeyboardHide(this, null);
            KeyboardSlide.Completed += KeyboardSlide_SlideUp;
            KeyboardSlide.Begin();
        }

        public void Dismiss()
        {
            SIP.Instance.Hide();
            KeyboardHide(this, null);
            SlideKeyFrame.Value = this.Height;
            KeyboardSlide.Begin();
        }

        private void KeyboardSlide_SlideUp(object sender, EventArgs e)
        {
            SIP.Instance.Show();
            SlideKeyFrame.Value = -this.Height;
            KeyboardSlide.Completed -= KeyboardSlide_SlideUp;
            KeyboardSlide.Begin();
            KeyboardShow(this, null);        
        }        
    }
}